/**
 * Octopus, an XMMS2 client.
 * Copyright (C) 2014 Octopus Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace OctopusUi {
	public class UiPlaylistPanel : Mx.Frame, IKeyFocusActor {
		public  const int PLAYLISTWIDTH = 320;
		private const int ICONSIZE = 20;

		private Mx.BoxLayout playlistPanelVBox;
		private OctopusMx.MxTracklist playlistPanelTracklist;
		private Clutter.Timeline timelineScrollUp;
		private Mx.Frame playlistPanelMouseOverScrollUp;
		private Clutter.Timeline timelineScrollDown;
		private Mx.Frame playlistPanelMouseOverScrollDown;

		public bool use_texture_cache { get; set; default = false; }

		private ulong signalId;
		private signal void insert_done ();
		public signal void show_message_bar_text (string message);

		public UiPlaylistPanel () {
			var playlistPanelStack = new Mx.Stack ();
			playlistPanelStack.name = "playlistPanel-background";
			playlistPanelStack.reactive = true;
			this.add_child (playlistPanelStack);
			playlistPanelStack.button_release_event.connect (() => {
				key_focus ();
				return false; //propagate
			});
			this.playlistPanelVBox = new Mx.BoxLayout ();
			this.playlistPanelVBox.style_class = "playlistPanel-vbox";
			this.playlistPanelVBox.orientation = Mx.Orientation.VERTICAL;
			this.playlistPanelVBox.width = PLAYLISTWIDTH;
			this.playlistPanelVBox.reactive = true;
			playlistPanelStack.add_child (this.playlistPanelVBox);
			//drop on empty playlist or append to playlist
			var playlistPanelVBoxDropAction = new Clutter.DropAction ();
			playlistPanelVBoxDropAction.over_in.connect ((/*ClutterDropAction*/ action,
			                                              /*ClutterActor*/      actor) => {
				this.playlistPanelVBox.style_pseudo_class_add ("drop-indicator");
			});
			playlistPanelVBoxDropAction.over_out.connect ((/*ClutterDropAction*/ action,
			                                               /*ClutterActor*/      actor) => {
				this.playlistPanelVBox.style_pseudo_class_remove ("drop-indicator");
			});
			playlistPanelVBoxDropAction.drop.connect ((/*ClutterDropAction*/ action,
			                                           /*ClutterActor*/      actor,
			                                           /*gfloat*/            event_x,
			                                           /*gfloat*/            event_y) => {
				this.playlistPanelVBox.style_pseudo_class_remove ("drop-indicator");
				if (this.playlistPanelTracklist == OctopusMx.MxTracklist.get_drag_actor ()) {
					//check status for move or copy
					if (OctopusMx.MxTracklist.get_drag_and_drop_status () == OctopusMx.MxTracklist.DragAndDropStatus.DRAG_MOVE) {
						this.signalId = this.insert_done.connect (() => {
							//moved "selected" tracks are removed from list
							if (OctopusMx.MxTracklist.get_drag_actor ().get_selected_size () > 0) {
								var drop_idx = this.playlistPanelTracklist.get_list_size () - 1;
								var drag_idx = this.playlistPanelTracklist.index_of (OctopusMx.MxTracklist.get_drag_actor ().get_selected (0));
								Octopus.Global.get ().get_active_playlist ().xmms_playlist_move_entry (drag_idx, drop_idx);
							} else {
								this.disconnect (this.signalId);
							}
						});
						//fake insert-done signal to get moving entries one by one going
						insert_done ();
					} else {
						//append playlist
						playlistAddSelected ();
					}
				} else {
					//append playlist
					playlistAddSelected ();
				}
				OctopusMx.MxTracklist.set_drag_and_drop_status (OctopusMx.MxTracklist.DragAndDropStatus.DROP_SUCCESS);
			});
			this.playlistPanelVBox.add_action (playlistPanelVBoxDropAction);

			var playlistPanelControl = new Mx.BoxLayout ();
			playlistPanelControl.style_class = "playlistPanel-control";
			playlistPanelControl.orientation = Mx.Orientation.HORIZONTAL;
			this.playlistPanelVBox.add_child (playlistPanelControl);
			populatePlaylistPanelControl (playlistPanelControl);

			addMouseOverScrollUp ();
			var playlistScrollView = new Mx.ScrollView ();
			playlistScrollView.style_class = "playlistPanel-scrollview";
			playlistScrollView.y_align = Clutter.ActorAlign.FILL;
			this.playlistPanelVBox.add_child (playlistScrollView);
			addMouseOverScrollDown ();
			playlistScrollView.notify["allocation"].connect (() => {
				//FIXME: do not create an allocation circle
				if (   (this.playlistPanelTracklist.vertical_adjustment.upper > playlistScrollView.height)
					&& (playlistScrollView.height > 0)) {
					this.playlistPanelMouseOverScrollUp.visible = true;
					this.playlistPanelMouseOverScrollDown.visible = true;
				} else {
					this.playlistPanelMouseOverScrollUp.visible = false;
					this.playlistPanelMouseOverScrollDown.visible = false;
					this.timelineScrollUp.stop ();
					this.timelineScrollDown.stop ();
				}
			});

			this.playlistPanelTracklist = new OctopusMx.MxTracklist ();
			this.playlistPanelTracklist.enable_drag_action ();
			this.playlistPanelTracklist.style_class = "playlistPanel-tracklist";
			this.playlistPanelTracklist.orientation = Mx.Orientation.VERTICAL;
			playlistScrollView.add_child (this.playlistPanelTracklist);
			this.playlistPanelTracklist.remove.connect ((/*MxTracklist*/ tracklist, /*MxFrameTrack*/ track) => {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_remove_entry((Octopus.ActivePlaylistEntry)track.track_model);
			});

			var playlistPanelOverlay = new Mx.Frame ();
			playlistPanelOverlay.name = "playlistPanel-overlay";
			playlistPanelStack.add_child (playlistPanelOverlay);

			Octopus.Global.get ().get_playback ().load_playback ();
			Octopus.Global.get ().get_active_playlist ().playlist_add.connect(on_playlistmodel_add);
			Octopus.Global.get ().get_active_playlist ().playlist_insert.connect(on_playlistmodel_insert);
			Octopus.Global.get ().get_active_playlist ().load_playlist ();
		}

		private void populatePlaylistPanelControl (Mx.BoxLayout playlistPanelControl) {
			//play/pause
			var controlPlayPause = new Mx.Button ();
			controlPlayPause.style_class = "controlPanel-play-pause";
			controlPlayPause.height = ICONSIZE;
			controlPlayPause.width  = ICONSIZE;
			controlPlayPause.is_toggle = true;
			playlistPanelControl.add_child (controlPlayPause);
			Octopus.Global.get ().get_playback ().playback_status_stop.connect (() => {
				controlPlayPause.toggled = false;
			});
			Octopus.Global.get ().get_playback ().playback_status_play.connect (() => {
				controlPlayPause.toggled = true;
			});
			Octopus.Global.get ().get_playback ().playback_status_pause.connect (() => {
				controlPlayPause.toggled = false;
			});
			controlPlayPause.clicked.connect (() => {
				if (controlPlayPause.toggled) {
					Octopus.Global.get ().get_playback ().xmms_play ();
				} else {
					Octopus.Global.get ().get_playback ().xmms_pause ();
				}
			});
			controlPlayPause.enter_event.connect (() => {
				show_message_bar_text ("play/pause");
				return false; //propagate
			});
			controlPlayPause.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
			//stop
			var controlStop = new Mx.Button ();
			controlStop.style_class = "controlPanel-stop";
			controlStop.height = ICONSIZE;
			controlStop.width  = ICONSIZE;
			playlistPanelControl.add_child (controlStop);
			controlStop.clicked.connect (() => {
				Octopus.Global.get ().get_playback ().xmms_stop ();
			});
			controlStop.enter_event.connect (() => {
				show_message_bar_text ("stop");
				return false; //propagate
			});
			controlStop.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
			//prev
			var controlPrev = new Mx.Button ();
			controlPrev.style_class = "controlPanel-prev";
			controlPrev.height = ICONSIZE;
			controlPrev.width  = ICONSIZE;
			playlistPanelControl.add_child (controlPrev);
			controlPrev.clicked.connect (() => {
				Octopus.Global.get ().get_playback ().xmms_set_prev ();
			});
			controlPrev.enter_event.connect (() => {
				show_message_bar_text ("previous");
				return false; //propagate
			});
			controlPrev.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
			//next
			var controlNext = new Mx.Button ();
			controlNext.style_class = "controlPanel-next";
			controlNext.height = ICONSIZE;
			controlNext.width  = ICONSIZE;
			playlistPanelControl.add_child (controlNext);
			controlNext.clicked.connect (() => {
				Octopus.Global.get ().get_playback ().xmms_set_next ();
			});
			controlNext.enter_event.connect (() => {
				show_message_bar_text ("next");
				return false; //propagate
			});
			controlNext.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});

			var controlSeparator = new Mx.Frame ();
			controlSeparator.style_class = "controlPanel-command-separator";
			controlSeparator.height = ICONSIZE;
			controlSeparator.width  = 125;
			playlistPanelControl.add_child (controlSeparator);

			//clear
			var playlistPanelControlClear = new Mx.Button ();
			playlistPanelControlClear.style_class = "playlistPanel-control-clear";
			playlistPanelControlClear.height = ICONSIZE;
			playlistPanelControlClear.width  = ICONSIZE;
			playlistPanelControl.add_child (playlistPanelControlClear);
			playlistPanelControlClear.clicked.connect (() => {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_clear ();
			});
			playlistPanelControlClear.enter_event.connect (() => {
				show_message_bar_text ("clear playlist");
				return false; //propagate
			});
			playlistPanelControlClear.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
			//repeat-all
			var playlistPanelControlRepeat = new Mx.Button ();
			playlistPanelControlRepeat.style_class = "playlistPanel-control-repeat";
			playlistPanelControlRepeat.height = ICONSIZE;
			playlistPanelControlRepeat.width  = ICONSIZE;
			playlistPanelControlRepeat.is_toggle = true;
			playlistPanelControl.add_child (playlistPanelControlRepeat);
			Octopus.Global.get ().get_active_playlist ().playlist_repeat_all_off.connect (() => {
				playlistPanelControlRepeat.toggled = false;
			});
			Octopus.Global.get ().get_active_playlist ().playlist_repeat_all_on.connect (() => {
				playlistPanelControlRepeat.toggled = true;
			});
			playlistPanelControlRepeat.clicked.connect (() => {
				if (playlistPanelControlRepeat.toggled) {
					Octopus.Global.get ().get_active_playlist ().xmms_playlist_repeat_all_on ();
				} else {
					Octopus.Global.get ().get_active_playlist ().xmms_playlist_repeat_all_off ();
				}
			});
			playlistPanelControlRepeat.enter_event.connect (() => {
				show_message_bar_text ("repeat all (on/off)");
				return false; //propagate
			});
			playlistPanelControlRepeat.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
			//repeat-one
			var playlistPanelControlRepeat1 = new Mx.Button ();
			playlistPanelControlRepeat1.style_class = "playlistPanel-control-repeat1";
			playlistPanelControlRepeat1.height = ICONSIZE;
			playlistPanelControlRepeat1.width  = ICONSIZE;
			playlistPanelControlRepeat1.is_toggle = true;
			playlistPanelControl.add_child (playlistPanelControlRepeat1);
			Octopus.Global.get ().get_active_playlist ().playlist_repeat_one_off.connect (() => {
				playlistPanelControlRepeat1.toggled = false;
			});
			Octopus.Global.get ().get_active_playlist ().playlist_repeat_one_on.connect (() => {
				playlistPanelControlRepeat1.toggled = true;
			});
			playlistPanelControlRepeat1.clicked.connect (() => {
				if (playlistPanelControlRepeat1.toggled) {
					Octopus.Global.get ().get_active_playlist ().xmms_playlist_repeat_one_on ();
				} else {
					Octopus.Global.get ().get_active_playlist ().xmms_playlist_repeat_one_off ();
				}
			});
			playlistPanelControlRepeat1.enter_event.connect (() => {
				show_message_bar_text ("repeat one (on/off)");
				return false; //propagate
			});
			playlistPanelControlRepeat1.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
			//shuffle
			var playlistPanelControlShuffle = new Mx.Button ();
			playlistPanelControlShuffle.style_class = "playlistPanel-control-shuffle";
			playlistPanelControlShuffle.height = ICONSIZE;
			playlistPanelControlShuffle.width  = ICONSIZE;
			playlistPanelControl.add_child (playlistPanelControlShuffle);
			playlistPanelControlShuffle.clicked.connect (() => {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_shuffle ();
			});
			playlistPanelControlShuffle.enter_event.connect (() => {
				show_message_bar_text ("shuffle");
				return false; //propagate
			});
			playlistPanelControlShuffle.leave_event.connect (() => {
				show_message_bar_text ("");
				return false; //propagate
			});
		}

		private void addMouseOverScrollUp () {
			this.timelineScrollUp = new Clutter.Timeline (100);
			this.timelineScrollUp.delay = 250;
			this.timelineScrollUp.repeat_count = -1;
			this.timelineScrollUp.completed.connect (() => {
				//scroll up step increment
				this.playlistPanelTracklist.vertical_adjustment.value =
				    this.playlistPanelTracklist.vertical_adjustment.value
				  - this.playlistPanelTracklist.vertical_adjustment.step_increment;
			});
			this.playlistPanelMouseOverScrollUp = new Mx.Frame ();
			this.playlistPanelMouseOverScrollUp.style_class = "playlistPanel-mouse-over-scroll-up";
			this.playlistPanelMouseOverScrollUp.reactive = true;
			this.playlistPanelMouseOverScrollUp.height = 10;
			this.playlistPanelMouseOverScrollUp.enter_event.connect (() => {
				this.timelineScrollUp.start ();
				return false; //propagate
			});
			this.playlistPanelMouseOverScrollUp.leave_event.connect (() => {
				this.timelineScrollUp.stop ();
				return false; //propagate
			});
			var scrollUpDropAction = new Clutter.DropAction ();
			scrollUpDropAction.over_in.connect (() => {
				this.timelineScrollUp.start ();
			});
			scrollUpDropAction.over_out.connect (() => {
				this.timelineScrollUp.stop ();
			});
			this.playlistPanelMouseOverScrollUp.add_action (scrollUpDropAction);
			this.playlistPanelVBox.add_child (this.playlistPanelMouseOverScrollUp);
		}

		private void addMouseOverScrollDown () {
			this.timelineScrollDown = new Clutter.Timeline (100);
			this.timelineScrollDown.delay = 250;
			this.timelineScrollDown.repeat_count = -1;
			this.timelineScrollDown.completed.connect (() => {
				//scroll up step increment
				this.playlistPanelTracklist.vertical_adjustment.value =
				    this.playlistPanelTracklist.vertical_adjustment.value
				  + this.playlistPanelTracklist.vertical_adjustment.step_increment;
			});
			this.playlistPanelMouseOverScrollDown = new Mx.Frame ();
			this.playlistPanelMouseOverScrollDown.style_class = "playlistPanel-mouse-over-scroll-down";
			this.playlistPanelMouseOverScrollDown.reactive = true;
			this.playlistPanelMouseOverScrollDown.height = 10;
			this.playlistPanelMouseOverScrollDown.enter_event.connect (() => {
				this.timelineScrollDown.start ();
				return false; //propagate
			});
			this.playlistPanelMouseOverScrollDown.leave_event.connect (() => {
				this.timelineScrollDown.stop ();
				return false; //propagate
			});
			var scrollDownDropAction = new Clutter.DropAction ();
			scrollDownDropAction.over_in.connect (() => {
				this.timelineScrollDown.start ();
			});
			scrollDownDropAction.over_out.connect (() => {
				this.timelineScrollDown.stop ();
			});
			this.playlistPanelMouseOverScrollDown.add_action (scrollDownDropAction);
			this.playlistPanelVBox.add_child (this.playlistPanelMouseOverScrollDown);
		}

		private void on_playlistmodel_add (Octopus.ActivePlaylist playlist_model, Octopus.ActivePlaylistEntry track_model) {
			var track = create_track (track_model);
			this.playlistPanelTracklist.add_track (track);
		}

		private void on_playlistmodel_insert (Octopus.ActivePlaylist playlist_model, Octopus.ActivePlaylistEntry track_model, int pos) {
			var track = create_track (track_model);
			this.playlistPanelTracklist.add_track_at_pos(track, pos);
			insert_done ();
		}

		private OctopusMx.MxFrameTrack create_track (Octopus.ActivePlaylistEntry track_model) {
			var track = new OctopusMx.MxFrameTrack (track_model, "playlistentry-background", "playlistentry-overlay");
			track.reactive = true;
			track.enable_drop_action ();
			track.button_release_event.connect ((/*Clutter.ButtonEvent*/ event) => {
				if ((event.button == 1) && (event.click_count == 1)) {  //left mouse button
					if ((event.modifier_state & Clutter.ModifierType.CONTROL_MASK) == Clutter.ModifierType.CONTROL_MASK) {
						//toggle selected
						this.playlistPanelTracklist.toggle_selected (track);
					} else if ((event.modifier_state & Clutter.ModifierType.SHIFT_MASK) == Clutter.ModifierType.SHIFT_MASK) {
						//select all actors between this and the last selected actor
						this.playlistPanelTracklist.multi_select (track);
					} else {
						//select one actor, unselect all others
						this.playlistPanelTracklist.unselect_tracklist ();
						this.playlistPanelTracklist.set_selected (track);
					}
				}
				if ((event.button == 1) && (event.click_count == 2)) { //double click (left)
					Octopus.Global.get ().get_active_playlist ().xmms_playlist_set_next ((Octopus.ActivePlaylistEntry)track.track_model);
				}
				if ((event.button == 3) && (event.click_count == 1)) {  //right mouse button
				}
				return false; //propagate
			});

			var playlistEntryContent = new PlaylistEntryContent (track_model, this.use_texture_cache);
			track.get_track_content ().add_child (playlistEntryContent);

			var playlistEntryMouseOverMenu = new PlaylistEntryMouseOverMenu (track_model, track, this.playlistPanelTracklist);
			track.get_track_mouse_over_menu ().add_child (playlistEntryMouseOverMenu);

			track_model.remove.connect (() => {
				this.playlistPanelTracklist.remove_track (track);
			});

			var playlistEntryDragHandleContent = new PlaylistEntryDragHandleContent (track_model);
			track.get_drag_handle ().add_child (playlistEntryDragHandleContent);

			track.drop_move.connect (() => {
				//process of moving a track in playlist:
				// (1) fake "insert-done" signal to start process
				// (2) playlist_move_entry (xmms)
				// (3) remove entry in model -> remove entry from view
				// (4) insert entry into model -> insert entry into view -> "insert-done" signal
				// (5) if selected tracks > 0 repeat at (2), else we are done
				//move tracks one by one
				this.signalId = this.insert_done.connect(() => {
					//moved "selected" tracks are removed from list
					if (OctopusMx.MxTracklist.get_drag_actor ().get_selected_size () > 0) {
						var drop_idx = ((OctopusMx.MxTracklist)track.get_parent ()).index_of (track);
						var drag_idx = ((OctopusMx.MxTracklist)track.get_parent ()).index_of (OctopusMx.MxTracklist.get_drag_actor ().get_selected (0));
						if (drag_idx > drop_idx) { //move up
							Octopus.Global.get ().get_active_playlist ().xmms_playlist_move_entry (drag_idx, drop_idx);
						}
						if (drag_idx < drop_idx) { //move down
							Octopus.Global.get ().get_active_playlist ().xmms_playlist_move_entry (drag_idx, (drop_idx - 1));
						}
					} else {
						this.disconnect (this.signalId);
					}
				});
				//fake insert-done signal to get moving entries one by one going
				this.insert_done ();
			});
			track.drop_copy.connect (playlistInsertSelected);
			track.drop_insert.connect (playlistInsertSelected);

			return track;
		}

		private void playlistAddSelected () {
			for (int i = 0; i < OctopusMx.MxTracklist.get_drag_actor ().get_selected_size (); i++) {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_add (OctopusMx.MxTracklist.get_drag_actor ().get_selected (i).track_model.id);
			}
		}

		private void playlistInsertSelected (OctopusMx.MxFrameTrack track) {
			int drop_idx = ((OctopusMx.MxTracklist)track.get_parent ()).index_of (track);
			for (int i = 0; i < OctopusMx.MxTracklist.get_drag_actor ().get_selected_size (); i++) {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_insert (drop_idx, OctopusMx.MxTracklist.get_drag_actor ().get_selected (i).track_model.id);
				drop_idx++;
			}
		}

		public void onKeyPressEvent (Gdk.EventKey event) {
			if (event.keyval == Gdk.Key.Home) { //Home
				this.playlistPanelTracklist.vertical_adjustment.value = 0;
			}
			if (event.keyval == Gdk.Key.End) { //End
				this.playlistPanelTracklist.vertical_adjustment.value = this.playlistPanelTracklist.vertical_adjustment.upper;
			}
			if (event.keyval == Gdk.Key.Page_Up) { //Page_Up
				this.playlistPanelTracklist.vertical_adjustment.value =
					this.playlistPanelTracklist.vertical_adjustment.value
				  - this.playlistPanelTracklist.vertical_adjustment.page_increment;
			}
			if (event.keyval == Gdk.Key.Page_Down) { //Page_Down
				this.playlistPanelTracklist.vertical_adjustment.value =
					this.playlistPanelTracklist.vertical_adjustment.value
				  + this.playlistPanelTracklist.vertical_adjustment.page_increment;
			}
			if (event.keyval == Gdk.Key.Delete) { //Delete
				this.playlistPanelTracklist.remove_selected ();
			}
			if (event.keyval == Gdk.Key.Up) { //Up
				if ((event.state & Gdk.ModifierType.SHIFT_MASK) == Gdk.ModifierType.SHIFT_MASK) {
					this.playlistPanelTracklist.add_selected_prev ();
				}
				this.playlistPanelTracklist.vertical_adjustment.value =
					this.playlistPanelTracklist.vertical_adjustment.value
				  - this.playlistPanelTracklist.vertical_adjustment.step_increment;
			}
			if (event.keyval == Gdk.Key.Down) { //Down
				if ((event.state & Gdk.ModifierType.SHIFT_MASK) == Gdk.ModifierType.SHIFT_MASK) {
					this.playlistPanelTracklist.add_selected_next ();
				}
				this.playlistPanelTracklist.vertical_adjustment.value =
					this.playlistPanelTracklist.vertical_adjustment.value
				  + this.playlistPanelTracklist.vertical_adjustment.step_increment;
			}
		}
	}

	public class PlaylistEntryContent : Mx.Frame {
		private const int ALBUMARTSIZE = 40;
		private const int TITLEWIDTH = 215;
		private const int DURATIONWIDTH = 45;

		private Octopus.ActivePlaylistEntry track_model;

		public PlaylistEntryContent (Octopus.ActivePlaylistEntry track_model, bool use_texture_cache) {
			this.track_model = track_model;
			var playlistEntryLayoutBoxHorizontal = new Mx.BoxLayout ();
			playlistEntryLayoutBoxHorizontal.orientation = Mx.Orientation.HORIZONTAL;
			this.add_child (playlistEntryLayoutBoxHorizontal);

			var playlistEntryAlbumArtStack = new Mx.Stack ();
			playlistEntryAlbumArtStack.style_class = "playlistentry-album-art-background";
			playlistEntryAlbumArtStack.width = ALBUMARTSIZE;
			playlistEntryAlbumArtStack.height = ALBUMARTSIZE;
			playlistEntryLayoutBoxHorizontal.add_child (playlistEntryAlbumArtStack);
			var albumArt = new UiAlbumArt (ALBUMARTSIZE, use_texture_cache);
			playlistEntryAlbumArtStack.add_child (albumArt);

			var playlistEntryCurrentPos = new Mx.Frame ();
			playlistEntryCurrentPos.style_class = "playlistentry-current-pos";
			playlistEntryCurrentPos.visible = false;
			playlistEntryCurrentPos.width = ALBUMARTSIZE;
			playlistEntryCurrentPos.height = ALBUMARTSIZE;
			playlistEntryAlbumArtStack.add_child (playlistEntryCurrentPos);

			var playlistEntryAlbumArtOverlay = new Mx.Frame ();
			playlistEntryAlbumArtOverlay.style_class = "playlistentry-album-art-overlay";
			playlistEntryAlbumArtStack.add_child (playlistEntryAlbumArtOverlay);

			var playlistEntryTitleArtistVBox = new Mx.BoxLayout ();
			playlistEntryTitleArtistVBox.orientation = Mx.Orientation.VERTICAL;
			playlistEntryLayoutBoxHorizontal.add_child (playlistEntryTitleArtistVBox);

			var playlistEntryTitle = new Mx.Label ();
			playlistEntryTitle.style_class = "playlistentry-title";
			playlistEntryTitle.width = TITLEWIDTH;
			playlistEntryTitleArtistVBox.add_child (playlistEntryTitle);

			var playlistEntryArtist = new Mx.Label ();
			playlistEntryArtist.style_class = "playlistentry-artist";
			playlistEntryArtist.width = TITLEWIDTH;
			playlistEntryTitleArtistVBox.add_child (playlistEntryArtist);

			var playlistEntryDuration = new Mx.Label ();
			playlistEntryDuration.style_class = "playlistentry-duration";
			playlistEntryDuration.width = DURATIONWIDTH;
			playlistEntryLayoutBoxHorizontal.add_child (playlistEntryDuration);

			track_model.medialib_info_changed.connect (() => {
				albumArt.loadAlbumArt (this.track_model.picture_front);
				playlistEntryTitle.text    = this.track_model.title;
				playlistEntryDuration.text = Octopus.Utils.format_duration (this.track_model.duration);
				playlistEntryArtist.text   = this.track_model.artist;
			});
			track_model.notify["current-pos"].connect (() => {
				playlistEntryCurrentPos.visible = this.track_model.current_pos;
			});
		}
	}

	public class PlaylistEntryMouseOverMenu : Mx.Frame {
		private const int MENUICONSIZE = 20;

		private Octopus.ActivePlaylistEntry track_model;
		private OctopusMx.MxFrameTrack track;
		private OctopusMx.MxTracklist tracklist;

		public PlaylistEntryMouseOverMenu (Octopus.ActivePlaylistEntry track_model, OctopusMx.MxFrameTrack track, OctopusMx.MxTracklist tracklist) {
			this.track_model = track_model;
			this.track =  track;
			this.tracklist = tracklist;
			//mouse-over-menu buttons
			var playlistEntryMenuLayout = new Mx.BoxLayout ();
			playlistEntryMenuLayout.style_class = "playlistentry-menu-layout";
			playlistEntryMenuLayout.orientation = Mx.Orientation.HORIZONTAL;
			playlistEntryMenuLayout.x_align = Clutter.ActorAlign.END;
			playlistEntryMenuLayout.y_align = Clutter.ActorAlign.END;
			this.add_child (playlistEntryMenuLayout);

			//play
			var playlistEntryMenuPlay = new Mx.Button ();
			playlistEntryMenuPlay.style_class = "playlistentry-menu-play";
			playlistEntryMenuPlay.height = MENUICONSIZE;
			playlistEntryMenuPlay.width = MENUICONSIZE;
			playlistEntryMenuLayout.add_child (playlistEntryMenuPlay);
			playlistEntryMenuPlay.clicked.connect (() => {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_set_next (this.track_model);
			});
			//selected
			var playlistEntryMenuSelected = new Mx.Button ();
			playlistEntryMenuSelected.style_class = "playlistentry-menu-selected";
			playlistEntryMenuSelected.height = MENUICONSIZE;
			playlistEntryMenuSelected.width = MENUICONSIZE;
			playlistEntryMenuSelected.is_toggle = true;
			playlistEntryMenuLayout.add_child (playlistEntryMenuSelected);
			playlistEntryMenuSelected.clicked.connect (() => {
				this.tracklist.toggle_selected (this.track);
			});
			track.notify["selected"].connect (() => {
				if (this.track.selected) {
					playlistEntryMenuSelected.toggled = true;
				} else {
					playlistEntryMenuSelected.toggled = false;
				}
			});
			//delete
			var playlistEntryMenuDelete = new Mx.Button ();
			playlistEntryMenuDelete.style_class = "playlistentry-menu-delete";
			playlistEntryMenuDelete.height = MENUICONSIZE;
			playlistEntryMenuDelete.width = MENUICONSIZE;
			playlistEntryMenuLayout.add_child (playlistEntryMenuDelete);
			playlistEntryMenuDelete.clicked.connect (() => {
				Octopus.Global.get ().get_active_playlist ().xmms_playlist_remove_entry (this.track_model);
			});
		}
	}

	public class PlaylistEntryDragHandleContent : Mx.Frame {
		private const int TITLEWIDTH = 215;
		private Octopus.ActivePlaylistEntry track_model;

		public PlaylistEntryDragHandleContent (Octopus.ActivePlaylistEntry track_model) {
			this.track_model = track_model;

			var playlistEntryBackground = new Mx.Frame ();
			playlistEntryBackground.style_class = "playlistentrydraghandle-background";
			this.add_child (playlistEntryBackground);

			var playlistEntryTitleArtistVBox = new Mx.BoxLayout ();
			playlistEntryTitleArtistVBox.orientation = Mx.Orientation.VERTICAL;
			playlistEntryBackground.add_child (playlistEntryTitleArtistVBox);

			var playlistEntryTitle = new Mx.Label ();
			playlistEntryTitle.style_class = "playlistentrydraghandle-title";
			playlistEntryTitle.width = TITLEWIDTH;
			playlistEntryTitle.text = track_model.title;
			playlistEntryTitleArtistVBox.add_child (playlistEntryTitle);

			var playlistEntryArtist = new Mx.Label ();
			playlistEntryArtist.style_class = "playlistentrydraghandle-artist";
			playlistEntryArtist.text = track_model.artist;
			playlistEntryTitleArtistVBox.add_child (playlistEntryArtist);

			track_model.medialib_info_changed.connect (() => {
				playlistEntryTitle.text = this.track_model.title;
				playlistEntryArtist.text = this.track_model.artist;
			});
		}
	}
}
